# plugs/karma.py
#
#

""" karma plugin """

__copyright__ = 'this file is in the public domain'

from gozerbot.commands import cmnds
from gozerbot.examples import examples
from gozerbot.redispatcher import rebefore
from gozerbot.datadir import datadir
from gozerbot.generic import handle_exception, rlog, lockdec
from gozerbot.utils.statdict import Statdict
from gozerbot.aliases import aliases
from gozerbot.plughelp import plughelp
from gozerbot.config import config
from gplugs.links import links
if not config['nodb']:
    from gozerbot.database.db import db
import thread, pickle, time, os

plughelp.add('karma', 'maintain karma of items .. use ++ to raise karma by 1 \
or use -- to lower by 1 .. reason might be given after a "#"')

savelist = []

class Karma:

    """ holds karma data """

    def __init__(self, ddir):
        rlog(0, 'karma', 'reading %s' % datadir + os.sep + 'karma')
        self.datadir = ddir
        self.lock = thread.allocate_lock()
        try:
            karmafile = open(ddir + os.sep + 'karma', 'r')
            self.karma = pickle.load(karmafile)
            karmafile.close()
        except:
            self.karma = {}
        try:
            reasonupfile = open(ddir + os.sep + 'reasonup', 'r')
            self.reasonup = pickle.load(reasonupfile)
            reasonupfile.close()
        except:
            self.reasonup = {}
        try:
            reasondownfile = open(ddir + os.sep + 'reasondown', 'r')
            self.reasondown = pickle.load(reasondownfile)
            reasondownfile.close()
        except:
            self.reasondown = {}
        try:
            whoupfile = open(ddir + os.sep + 'whoup', 'r')
            self.whoup = pickle.load(whoupfile)
            whoupfile.close()
        except:
            self.whoup = {}
        try:
            whodownfile = open(ddir + os.sep + 'whodown', 'r')
            self.whodown = pickle.load(whodownfile)
            whodownfile.close()
        except:
            self.whodown = {}

    def size(self):
        return len(self.karma)
        
    def save(self):
        """ save karma data """
        try:
            self.lock.acquire()
            karmafile = open(self.datadir + os.sep + 'karma', 'w')
            pickle.dump(self.karma, karmafile)
            karmafile.close()
            rlog(1, 'karma', '%s karma saved' % self.datadir)
            reasonupfile = open(self.datadir + os.sep + 'reasonup', 'w')
            pickle.dump(self.reasonup, reasonupfile)
            reasonupfile.close()
            rlog(1, 'karma', '%s reasonup saved' % self.datadir)
            reasondownfile = open(self.datadir + os.sep + 'reasondown', 'w')
            pickle.dump(self.reasondown, reasondownfile)
            reasondownfile.close()
            rlog(1, 'karma', '%s reasondown saved' % self.datadir)
            whoupfile = open(self.datadir + os.sep + 'whoup', 'w')
            pickle.dump(self.whoup, whoupfile)
            whoupfile.close()
            rlog(1, 'karma', '%s whoup saved' % self.datadir)
            whodownfile = open(self.datadir + os.sep + 'whodown', 'w')
            pickle.dump(self.whoup, whodownfile)
            whodownfile.close()
            rlog(1, 'karma', '%s whodown saved' % self.datadir)
        finally:
            self.lock.release()

    def add(self, item, value):
        """ set karma value of item """
        self.karma[item.lower()] = value

    def delete(self, item):
        """ delete karma item """
        item = item.lower()
        try:
            del self.karma[item]
            return 1
        except KeyError:
            return 0

    def get(self, item):
        """ get karma of item """
        item = item.lower()
        if self.karma.has_key(item):
            return self.karma[item]
        else:
            return None

    def addwhy(self, item, updown, reason):
        """ add why of karma up/down """
        item = item.lower()
        if not self.karma.has_key(item):
            return 0
        reason = reason.strip()
        if updown == 'up':
            if self.reasonup.has_key(item):
                self.reasonup[item].append(reason)
            else:
                self.reasonup[item] = [reason]
        elif updown == 'down':
            if self.reasondown.has_key(item):
                self.reasondown[item].append(reason)
            else:
                self.reasondown[item] = [reason]
            
    def upitem(self, item, reason=None):
        """ up a karma item with/without reason """
        item = item.lower()
        if self.karma.has_key(item):
            self.karma[item] += 1
        else:
            self.karma[item] = 1
        if reason:
            reason = reason.strip()
            if self.reasonup.has_key(item):
                self.reasonup[item].append(reason)
            else:
                self.reasonup[item] = [reason]

    def down(self, item, reason=None):
        """ lower a karma item with/without reason """
        item = item.lower()
        if self.karma.has_key(item):
            self.karma[item] -= 1
        else:
            self.karma[item] = -1
        if reason:
            reason = reason.strip()
            if self.reasondown.has_key(item):
                self.reasondown[item].append(reason)
            else:
                self.reasondown[item] = [reason]

    def whykarmaup(self, item):
        """ get why of karma ups """
        item = item.lower()
        if self.reasonup.has_key(item):
            return self.reasonup[item]

    def whykarmadown(self, item):
        """ get why of karma downs """
        item = item.lower()
        if self.reasondown.has_key(item):
            return self.reasondown[item]

    def setwhoup(self, item, nick):
        """ set who upped a karma item """
        item = item.lower()
        if self.whoup.has_key(item):
            self.whoup[item].append(nick)
        else:
            self.whoup[item] = [nick]

    def setwhodown(self, item, nick):
        """ set who lowered a karma item """
        item = item.lower()
        if self.whodown.has_key(item):
            self.whodown[item].append(nick)
        else:
            self.whodown[item] = [nick]

    def getwhoup(self, item):
        """ get list of who upped a karma item """
        item = item.lower()
        try:
            return self.whoup[item]
        except KeyError:
            return None

    def getwhodown(self, item):
        """ get list of who downed a karma item """
        item = item.lower()
        try:
            return self.whodown[item]
        except KeyError:
            return None

    def good(self, limit=10):
        """ show top 10 of karma items """
        statdict = Statdict()
        for i in self.karma.keys():
            if i.startswith('quote '):
                continue
            statdict.upitem(i, value=self.karma[i])
        return statdict.top(limit=limit)

    def bad(self, limit=10):
        """ show lowest 10 of negative karma items """
        statdict = Statdict()
        for i in self.karma.keys():
            if i.startswith('quote '):
                continue
            statdict.upitem(i, value=self.karma[i])
        return statdict.down(limit=limit)

    def quotegood(self, limit=10):
        """ show top 10 of karma items """
        statdict = Statdict()
        for i in self.karma.keys():
            if not i.startswith('quote '):
                continue
            statdict.upitem(i, value=self.karma[i])
        return statdict.top(limit=limit)

    def quotebad(self, limit=10):
        """ show lowest 10 of negative karma items """
        statdict = Statdict()
        for i in self.karma.keys():
            if not i.startswith('quote '):
                continue
            statdict.upitem(i, value=self.karma[i])
        return statdict.down(limit=limit)

    def search(self, item):
        """ search karma items """
        result = []
        item = item.lower()
        for i, j in self.karma.iteritems():
            if item in i:
                result.append((i, j))
        return result

    def whatup(self, nick):
        """ show what items where upped by nick """
        nick = nick.lower()
        statdict = Statdict()
        for i, j in self.whoup.iteritems():
            for z in j:
                if nick == z:
                    statdict.upitem(i)
        return statdict.top()

    def whatdown(self, nick):
        """ show what items where lowered by nick """
        nick = nick.lower()
        statdict = Statdict()
        for i, j in self.whodown.iteritems():
            for z in j:
                if nick == z:
                    statdict.upitem(i)
        return statdict.top()

class KarmaDb(object):

    """ karma object """

    from gozerbot.database.db import db

    def save(self):
        pass

    def size(self):
        """ return number of karma items """
        nritems = db.execute(""" SELECT COUNT(*) FROM karma """)
        return nritems[0][0]

    def get(self, item):
        """ get karma of item """
        item = item.lower()
        value = db.execute(""" SELECT value FROM karma WHERE item = %s """, \
item)
        if value:
            return value[0][0]
        else:
            return 0

    def delete(self, item):
        """ delete karma item """
        item = item.lower()
        value = db.execute(""" DELETE FROM karma WHERE item = %s """, item)
        return value

    def addwhy(self, item, updown, reason):
        """ add why of karma up/down """
        item = item.lower()
        result = db.execute(""" INSERT INTO whykarma(item, updown, why) \
VALUES (%s, %s, %s) """, (item, updown, reason))
        return result
            
    def upitem(self, item, reason=None):
        """ up a karma item with/without reason """
        item = item.lower()
        value = db.execute(""" SELECT value FROM karma WHERE item = %s """, \
item)
        try:
            val = value[0][0]
        except (TypeError, IndexError):
            result = db.execute(""" INSERT INTO karma(item, value) VALUES \
(%s, 1) """, item)
            if reason:
                self.addwhy(item, 'up', reason.strip())
            return result
        val += 1
        result = db.execute(""" UPDATE karma SET value = %s WHERE item = %s \
""", (val, item))
        if reason:
            self.addwhy(item, 'up', reason.strip())
        return result

    def down(self, item, reason=None):
        """ lower a karma item with/without reason """
        item = item.lower()
        value = db.execute(""" SELECT value FROM karma WHERE item = %s """, \
item)
        try:
            val = value[0][0]
        except (TypeError, IndexError):
            result = db.execute(""" INSERT INTO karma(item, value) VALUES \
(%s, -1) """, item)
            if reason:
                self.addwhy(item, 'down', reason.strip())
            return result
        val -= 1
        result = db.execute(""" UPDATE karma SET value = %s WHERE item = %s \
""", (val, item))
        if reason:
            self.addwhy(item, 'down', reason.strip())
        return result

    def whykarmaup(self, item):
        """ get why of karma ups """
        item = item.lower()
        result = db.execute(""" SELECT why FROM whykarma WHERE \
item = %s AND updown = 'up' """, item)
        res = []
        if result:
             for i in result:
                 res.append(i[0])
        return res

    def whykarmadown(self, item):
        """ get why of karma downs """
        item = item.lower()
        result = db.execute(""" SELECT why FROM whykarma WHERE \
item = %s AND updown = 'down' """, item)
        res = []
        if result:
             for i in result:
                 res.append(i[0])
        return res

    def setwhoup(self, item, nick):
        """ set who upped a karma item """
        item = item.lower()
        nick = nick.lower()
        result = db.execute(""" INSERT INTO whokarma(item, nick, updown) \
VALUES(%s, %s, %s) """, (item, nick, 'up'))
        return result
 
    def setwhodown(self, item, nick):
        """ set who lowered a karma item """
        item = item.lower()
        nick = nick.lower()
        result = db.execute(""" INSERT INTO whokarma(item, nick, updown) \
VALUES(%s, %s, %s) """, (item, nick, 'down'))
        return result

    def getwhoup(self, item):
        """ get list of who upped a karma item """
        item = item.lower()
        result = db.execute(""" SELECT nick FROM whokarma WHERE item = %s \
AND updown = 'up' """, item)
        res = []
        if result:
             for i in result:
                 res.append(i[0])
        return res

    def getwhodown(self, item):
        """ get list of who downed a karma item """
        item = item.lower()
        result = db.execute(""" SELECT nick FROM whokarma WHERE item = %s \
AND updown = 'down' """, item)
        res = []
        if result:
             for i in result:
                 res.append(i[0])
        return res

    def search(self, item):
        """ search for matching karma item """
        item = item.lower()
        result = db.execute(""" SELECT item,value FROM karma WHERE item LIKE \
%s """, '%%%s%%' % item)
        res = []
        if result:
             for i in result:
                 res.append(i)
        return res

    def good(self, limit=10):
        """ show top 10 of karma items """
        statdict = Statdict()
        result = db.execute(""" SELECT item, value FROM karma """)
        if not result:
            return []
        for i in result:
            if i[0].startswith('quote '):
                continue
            statdict.upitem(i[0], value=i[1])
        return statdict.top(limit=limit)

    def bad(self, limit=10):
        """ show lowest 10 of negative karma items """
        statdict = Statdict()
        result = db.execute(""" SELECT item, value FROM karma """)
        if not result:
            return []
        for i in result:
            if i[0].startswith('quote '):
                continue
            statdict.upitem(i[0], value=i[1])
        return statdict.down(limit=limit)

    def quotegood(self, limit=10):
        """ show top 10 of karma items """
        statdict = Statdict()
        result = db.execute(""" SELECT item, value FROM karma """)
        if not result:
            return []
        for i in result:
            if not i[0].startswith('quote '):
                continue
            statdict.upitem(i[0], value=i[1])
        return statdict.top(limit=limit)

    def quotebad(self, limit=10):
        """ show lowest 10 of negative karma items """
        statdict = Statdict()
        result = db.execute(""" SELECT item, value FROM karma """)
        if not result:
            return []
        for i in result:
            if not i[0].startswith('quote '):
                continue
            statdict.upitem(i[0], value=i[1])
        return statdict.down(limit=limit)

    def whatup(self, nick):
        """ show what items are upped by nick """
        nick = nick.lower()
        statdict = Statdict()
        result = db.execute(""" SELECT item FROM whokarma WHERE nick = %s \
AND updown = 'up' """, nick)
        if not result:
            return []
        for i in result:
            statdict.upitem(i[0])
        return statdict.top()

    def whatdown(self, nick):
        """ show what items are lowered by nick """
        nick = nick.lower()
        statdict = Statdict()
        result = db.execute(""" SELECT item FROM whokarma WHERE nick = %s \
AND updown = 'down' """, nick)
        if not result:
            return []
        for i in result:
            statdict.upitem(i[0])
        return statdict.top()

if config['dbenable']:
    karma = KarmaDb()
else:
    karma = Karma(datadir)
assert(karma)

savelist.append(karma)

ratelimited = []
limiterlock = thread.allocate_lock()
limlock = lockdec(limiterlock)

def size():
    """ return number of kamra items """
    return karma.size()

@limlock
def ratelimit(bot, ievent):
    """ karma rate limiter """
    waittime = 30
    limit = 2
    try:
        name = ievent.userhost
        # Create a state for this user and his/her karma-state if necessary
        if not bot.state.has_key(name):
            bot.state[name] = {}
        if not bot.state[name].has_key('karma'):
            bot.state[name]['karma'] = {'count': 0, 'time': time.time() } 
        # If the waittime has elapsed, reset the counter
        if time.time() > (bot.state[name]['karma']['time'] + waittime):
            bot.state[name]['karma']['count'] = 0 
        # Update counter
        bot.state[name]['karma']['count'] += 1
        # If counter is too high, limit :)
        if bot.state[name]['karma']['count'] > limit:
            if name in ratelimited:
                return 0
            ievent.reply("karma limit reached, you'll have to wait %s \
seconds" % int((bot.state[name]['karma']['time'] + waittime) - time.time()))
            ratelimited.append(name)
            return 0
        # Update time
        bot.state[name]['karma']['time'] = time.time()
        # Ratelimiting passed :)
        try:
            ratelimited.remove(name)
        except ValueError:
            pass
        return 1
    except Exception, ex:
        handle_exception(ievent)

def handle_karmaget(bot, ievent):
    """ karma-get <item> .. show karma of item """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    else:
        item = ievent.rest
    result = karma.get(item)
    linklist = links[item]
    linkresult = ""
    total = 0
    if linklist:
        for i in linklist:
            linkkarma = karma.get(i)
            if linkkarma:
                total += linkkarma
                linkresult += "%s (%s) " % (i, linkkarma)
    if result:
        if linkresult:
            total += result
            ievent.reply("%s has karma %s .. %s .. total is %s" % (item, \
str(result), linkresult, total))
        else:
            ievent.reply("%s has karma %s" % (item, str(result)))
    else:
        ievent.reply("%s has no karma yet" % item)

cmnds.add('karma-get', handle_karmaget, ['USER', 'WEB', 'ANON', 'ANONKARMA'])
examples.add('karma-get', 'karma-get <item> .. show karma of <item>', \
'karma-get dunker')
aliases.data['karma'] = 'karma-get'

def handle_karmadel(bot, ievent):
    """ karma-del <item> .. delete karma item """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    item = ievent.rest
    result = karma.delete(item)
    if result:
        ievent.reply("%s deleted" % item)
    else:
        ievent.reply("can't delete %s" % item)

cmnds.add('karma-del', handle_karmadel, ['OPER'])
examples.add('karma-del', 'karma-del <item> .. delete karma item', \
'karma-del dunker')

def handle_karmaup(bot, ievent):
    """ <item>++ ['#' <reason>] .. increase karma of item with optional \
        reason """
    if not ratelimit(bot, ievent):
        return
    (item, reason) = ievent.groups
    item = item.strip().lower()
    karma.upitem(item, reason=reason)
    karma.setwhoup(item, ievent.nick)
    ievent.reply('karma of '+ item + ' is now ' + str(karma.get(item)))

rebefore.add(8, '^(.+)\+\+\s+#(.*)$', handle_karmaup, ['USER', 'KARMA', \
'ANONKARMA'],  allowqueue=False)
examples.add('++', "<item>++ ['#' <reason>] .. higher karma of item with 1 \
(use optional reason)", '1) gozerbot++ 2) gozerbot++ # top bot')

def handle_karmaup2(bot, ievent):
    """ increase karma without reason """
    ievent.groups += [None, ]
    handle_karmaup(bot, ievent)

rebefore.add(9, '^(.+)\+\+$', handle_karmaup2, ['USER', 'ANON', \
'ANONKARMA'], allowqueue=False)

def handle_karmadown(bot, ievent):
    """ <item>-- ['#' <reason> .. decrease karma item with reason """
    if not ratelimit(bot, ievent):
        return
    (item, reason) = ievent.groups
    item = item.strip().lower()
    karma.down(item, reason=reason)
    karma.setwhodown(item, ievent.nick)
    ievent.reply('karma of ' + item + ' is now ' + str(karma.get(item)))

rebefore.add(8, '^(.+)\-\-\s+#(.*)$', handle_karmadown, ['USER', 'KARMA', \
'ANONKARMA'], allowqueue=False)
examples.add('--', "<item>-- ['#' <reason>] .. lower karma of item with 1 \
(use optional reason)", '1) gozerbot-- 2) gozerbot-- # bad bot')

def handle_karmadown2(bot, ievent):
    """ decrease karma item without reason """
    ievent.groups += [None, ]
    handle_karmadown(bot, ievent)

rebefore.add(9, '^(.+)\-\-$', handle_karmadown2, ['USER', 'KARMA', \
'ANONKARMA'], allowqueue=False)

def handle_karmawhyup(bot, ievent):
    """ karma-whyup <item> .. show why karma of item has been increased """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    item = ievent.rest
    result = karma.whykarmaup(item)
    if result:
        ievent.reply('whykarmaup of %s: ' % item, result, dot=True)
    else:
        ievent.reply('%s has no reason for karmaup yet' % item)

cmnds.add('karma-whyup', handle_karmawhyup, ['USER', 'WEB', 'ANON', \
'ANONKARMA'])
examples.add('karma-whyup', 'karma-whyup <item> .. show the reason why \
karma of <item> was raised', 'karma-whyup gozerbot')
aliases.data['wku'] = 'karma-whyup'

def handle_whykarmadown(bot, ievent):
    """ karma-whydown <item> .. show why karma of item has been decreased """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    item = ievent.rest
    result = karma.whykarmadown(item)
    if result:
        ievent.reply('whykarmadown of %s: ' % item, result, dot=True)
    else:
        ievent.reply('%s has no reason for karmadown yet' % item)

cmnds.add('karma-whydown', handle_whykarmadown, ['USER', 'WEB', 'ANON', \
'ANONKARMA'])
examples.add('karma-whydown', 'karma-whydown <item> .. show the reason why \
karma of <item> was lowered', 'karma-whydown gozerbot')
aliases.data['wkd'] = 'karma-whydown'

def handle_karmagood(bot, ievent):
    """ karma-good .. show top 10 karma items """
    result = karma.good(limit=10)
    if result:
        res = []
        for i in result:
            if i[1] > 0:
                res.append("%s=%s" % (i[0], i[1]))
        ievent.reply('goodness: ', res, dot=True)
    else:
        ievent.reply('karma void')

cmnds.add('karma-good', handle_karmagood, ['USER', 'WEB', 'ANON', 'ANONKARMA'])
examples.add('karma-good', 'show top 10 karma', 'karma-good')
aliases.data['good'] = 'karma-good'

def handle_karmabad(bot, ievent):
    """ karma-bad .. show 10 most negative karma items """
    result = karma.bad(limit=10)
    if result:
        res = []
        for i in result:
            if i[1] < 0:
                res.append("%s=%s" % (i[0], i[1]))
        ievent.reply('badness: ', res, dot=True)
    else:
        ievent.reply('karma void')

cmnds.add('karma-bad', handle_karmabad, ['USER', 'WEB', 'ANON', 'ANONKARMA'])
examples.add('karma-bad', 'show lowest top 10 karma', 'karma-bad')
aliases.data['bad'] = 'karma-bad'

def handle_whokarmaup(bot, ievent):
    """ karma-whoup <item> .. show who increased a karma item """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    item = ievent.rest
    result = karma.getwhoup(item)
    statdict = Statdict()
    if result:
        for i in result:
            statdict.upitem(i)
        res = []
        for i in statdict.top():
            res.append("%s=%s" % i)
        ievent.reply("whokarmaup of %s: " % item, res, dot=True)
    else:
        ievent.reply('no whokarmaup data available for %s' % item)

cmnds.add('karma-whoup', handle_whokarmaup, ['USER', 'WEB', 'ANON', \
'ANONKARMA'])
examples.add('karma-whoup', 'karma-whoup <item> .. show who raised the \
karma of <item>', 'karma-whoup gozerbot')

def handle_whokarmadown(bot, ievent):
    """ karma-whodown <item> .. show who decreased a karma item """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    item = ievent.rest
    result = karma.getwhodown(item)
    statdict = Statdict()
    if result:
        for i in result:
            statdict.upitem(i)
        res = []
        for i in statdict.top():
            res.append("%s=%s" % i)
        ievent.reply("whokarmadown of %s: " % item, res, dot=True)
    else:
        ievent.reply('no whokarmadown data available for %s' % item)

cmnds.add('karma-whodown', handle_whokarmadown, ['USER', 'WEB', 'ANON', \
'ANONKARMA'])
examples.add('karma-whodown', 'karma-whodown <item> .. show who lowered \
the karma of <item>', 'karma-whodown gozerbot')

def handle_karmasearch(bot, ievent):
    """ karma-search <txt> .. search for karma items """
    what = ievent.rest
    if not what:
        ievent.missing('<txt>')
        return
    result = karma.search(what)
    if result:
        res = []
        for i in result:
            res.append("%s (%s)" % (i[0], i[1]))
        ievent.reply("karma items matching %s: " % what, res, dot=True)
    else:
        ievent.reply('no karma items matching %s found' % what)

cmnds.add('karma-search', handle_karmasearch, ['USER', 'WEB', 'ANON', \
'ANONKARMA'])
examples.add('karma-search', 'karma-search <txt> .. search karma' , \
'karma-search gozerbot')

def handle_karmawhatup(bot, ievent):
    """ show what karma items have been upped by nick """
    try:
        nick = ievent.args[0]
    except IndexError:
        ievent.missing('<nick>')
        return
    result = karma.whatup(nick)
    if result:
        res = []
        for i in result:
            res.append("%s (%s)" % i)
        ievent.reply("karma items upped by %s: " % nick, res, dot=True)
    else:
        ievent.reply('no karma items upped by %s' % nick)

cmnds.add('karma-whatup', handle_karmawhatup, ['USER', 'WEB', 'ANON', \
'ANONKARMA'])
examples.add('karma-whatup', 'karma-whatup <nick> .. show what karma items \
<nick> has upped' , 'karma-whatup dunker')

def handle_karmawhatdown(bot, ievent):
    """ show what karma items have been lowered by nick """
    try:
        nick = ievent.args[0]
    except IndexError:
        ievent.missing('<nick>')
        return
    result = karma.whatdown(nick)
    if result:
        res = []
        for i in result:
            res.append("%s (%s)" % i)
        ievent.reply("karma items downed by %s: " % nick, res, dot=True)
    else:
        ievent.reply('no karma items downed by %s' % nick)

cmnds.add('karma-whatdown', handle_karmawhatdown, ['USER', 'WEB', 'ANON', \
'ANONKARMA'])
examples.add('karma-whatdown', 'karma-whatdown <nick> .. show what karma \
items <nick> has downed' , 'karma-whatdown dunker')
